#ifndef __GLUTILS_H__
#define __GLUTILS_H__

#include <iostream>
#include <string>
#include <fstream>
#include <cmath>
#include <stack>

// $ sudo apt install libglew-dev
#include <GL/glew.h>

// $ sudo apt install libglfw3-dev
#include <GLFW/glfw3.h>

// $ git clone https://github.com/SpartanJ/SOIL2.git
// $ cd SOIL2/
// $ sudo apt install premake4
// $ premake4 gmake
// $ cd make/linux/
// $ make config=release
#include <SOIL2/SOIL2.h>

using namespace std;

class GLUtils {
public:
    static string readShaderSource(const char *filePath) {
        string content;
        ifstream fileStream(filePath, ios::in);
        string line = "";

        while(!fileStream.eof()) {
            getline(fileStream, line);
            content.append(line + "\n");
        }
        fileStream.close();
        cout << "readed content: " << endl << "-------------------------------\n" << content << endl;

        return content;
    }

    static void printShaderLog(GLuint shader){
        int len = 0;
        int chWrittn = 0;
        char *log;

        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);
        if(len > 0) {
            log = (char *)malloc(len);
            glGetShaderInfoLog(shader, len, &chWrittn, log);
            cout << "Shader Info Log: " << log << endl;
            free(log);
        }
    }

    static void printProgramLog(int prog){
        int len = 0;
        int chWrittn = 0;
        char *log = NULL;

        glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &len);
        if(len > 0) {
            log = (char *)malloc(len);
            glGetProgramInfoLog(prog, len, &chWrittn, log);
            cout << "Program Info Log: " << log << endl;
            free(log);
        }
    }

    static bool checkOpenGLError() {
        bool foundError = false;
        int glErr = glGetError();

        while(glErr != GL_NO_ERROR) {
            cout << "glError: " << glErr << endl;
            foundError = true;
            glErr = glGetError();
        }

        return foundError;
    }

    static GLuint createShaderProgram(const char *vShaderFile, const char *fShaderFile){
        GLint vertCompiled;
        GLint fragCompiled;
        GLint linked;

        GLuint vShader = glCreateShader(GL_VERTEX_SHADER);
        GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER);

        string vShaderSource = readShaderSource(vShaderFile);
        string fShaderSource = readShaderSource(fShaderFile);

        const char *vShaderStr = vShaderSource.c_str();
        const char *fShaderStr = fShaderSource.c_str();

        glShaderSource(vShader, 1, &vShaderStr, NULL);
        glShaderSource(fShader, 1, &fShaderStr, NULL);
        
        glCompileShader(vShader);
        checkOpenGLError();
        glGetShaderiv(vShader, GL_COMPILE_STATUS, &vertCompiled);
        if(vertCompiled != 1) {
            cout << "vertex compilation failed" << endl;
            printShaderLog(vShader);
        }

        glCompileShader(fShader);
        checkOpenGLError();
        glGetShaderiv(fShader, GL_COMPILE_STATUS, &fragCompiled);
        if(fragCompiled != 1) {
            cout << "fragment compilation failed" << endl;
            printShaderLog(fShader);
        }

        GLuint vfProgram = glCreateProgram();
        glAttachShader(vfProgram, vShader);
        glAttachShader(vfProgram, fShader);
        glLinkProgram(vfProgram);
        checkOpenGLError();
        glGetProgramiv(vfProgram, GL_LINK_STATUS, &linked);
        if(linked != 1) {
            cout << "linking failed" << endl;
            printProgramLog(vfProgram);
        }

        return vfProgram;
    }

    static GLuint loadTexture(const char *textImagePath) {
        GLuint textureId = 0;

        textureId = SOIL_load_OGL_texture(
            textImagePath,
            SOIL_LOAD_AUTO, 
            SOIL_CREATE_NEW_ID,  
            SOIL_FLAG_INVERT_Y);

        if(textureId == 0) {
            cout << "Could not find texture file " << textImagePath << endl;
        }

        return textureId;
    }
};

#endif